
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;

namespace ScrewTurn.Wiki {

	/// <summary>
	/// Implements a Users Storage Provider.
	/// </summary>
	[Serializable]
	public class UsersStorageProvider : IUsersStorageProvider {

		/// <summary>
		/// Used as lock object for multithreaded operations.
		/// </summary>
		private object locker = new object();
		private ComponentInformation info = new ComponentInformation("Local Users Provider", "ScrewTurn Software", "http://www.screwturn.eu");
		private IHost host;

		/// <summary>
		/// Initializes the Provider.
		/// </summary>
		/// <param name="host">The Host of the Provider.</param>
		/// <param name="config">The Configuration data, if any.</param>
		public void Init(IHost host, string config) {
			this.host = host;
		}

		/// <summary>
		/// Method invoked on shutdown.
		/// </summary>
		/// <remarks>This method might not be invoked in some cases.</remarks>
		public void Shutdown() { }

		/// <summary>
		/// Gets the Information about the Provider.
		/// </summary>
		public ComponentInformation Information {
			get { return info; }
		}

		/// <summary>
		/// Gets a value specifying whether the provider is read-only, i.e. it can only provide data and not store it.
		/// </summary>
		public bool ReadOnly {
			get { return false; }
		}

		/// <summary>
		/// Tests a Password for a User account.
		/// </summary>
		/// <param name="user">The User account.</param>
		/// <param name="password">The Password to test.</param>
		/// <returns>True if the Password is correct.</returns>
		public bool TestAccount(UserInfo user, string password) {
			return Hash.Compute(password).Equals(((LocalUserInfo)user).PasswordHash);
		}

		/// <summary>
		/// Gets all the Users.
		/// </summary>
		/// <remarks>The array is unsorted.</remarks>
		public UserInfo[] AllUsers {
			get {
				string tmp;
				lock(locker) {
					tmp = Tools.LoadFile(Settings.UsersFile).Replace("\r", "");
				}
				string[] lines = tmp.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
				UserInfo[] result = new UserInfo[lines.Length];
				string[] fields;
				for(int i = 0; i < lines.Length; i++) {
					fields = lines[i].Split('|');
					// Structure:
					// Username|PasswordHash|Email|Active-Inactive|DateTime|Admin-User
					result[i] = new LocalUserInfo(fields[0], fields[2], fields[3].ToLowerInvariant().Equals("active"),
						DateTime.Parse(fields[4]), fields[5].ToLowerInvariant().Equals("admin"), this, fields[1]);
				}
				return result;
			}
		}

		/// <summary>
		/// Searches for a User.
		/// </summary>
		/// <param name="user">The User to search for.</param>
		/// <returns>True if the User already exists.</returns>
		private bool Exists(UserInfo user) {
			UserInfo[] users = AllUsers;
			UsernameComparer comp = new UsernameComparer();
			for(int i = 0; i < users.Length; i++) {
				if(comp.Compare(users[i], user) == 0) return true;
			}
			return false;
		}

		/// <summary>
		/// Adds a new User.
		/// </summary>
		/// <param name="username">The Username.</param>
		/// <param name="password">The Password.</param>
		/// <param name="email">The Email address.</param>
		/// <param name="active">A value specifying whether or not the account is active.</param>
		/// <param name="dateTime">The Account creation Date/Time.</param>
		/// <param name="admin">A value specifying whether or not the User is an Administrator.</param>
		/// <returns>The correct UserInfo object or null.</returns>
		public UserInfo AddUser(string username, string password, string email, bool active, DateTime dateTime, bool admin) {
			if(Exists(new UserInfo(username, "", true, DateTime.Now, false, this))) return null;

			lock(locker) {
				BackupUsersFile();

				StringBuilder sb = new StringBuilder();
				sb.Append("\r\n"); // Important
				sb.Append(username);
				sb.Append("|");
				sb.Append(Hash.Compute(password));
				sb.Append("|");
				sb.Append(email);
				sb.Append("|");
				sb.Append(active ? "ACTIVE" : "INACTIVE");
				sb.Append("|");
				sb.Append(dateTime.ToString("yyyy'/'MM'/'dd' 'HH':'mm':'ss"));
				sb.Append("|");
				sb.Append(admin ? "ADMIN" : "USER");
				Tools.AppendFile(Settings.UsersFile, sb.ToString());
			}
			return new LocalUserInfo(username, email, active, dateTime, admin, this, Hash.Compute(password));
		}

		/// <summary>
		/// Sets the Active/Inactive status of a User.
		/// </summary>
		/// <param name="user">The User.</param>
		/// <param name="active">The status.</param>
		/// <returns>The correct UserInfo object.</returns>
		public UserInfo SetUserActivationStatus(UserInfo user, bool active) {
			lock(locker) {
				UserInfo[] users = AllUsers;
				UsernameComparer comp = new UsernameComparer();
				for(int i = 0; i < users.Length; i++) {
					if(comp.Compare(users[i], user) == 0) {
						LocalUserInfo tmp = new LocalUserInfo(user.Username, user.Email, active, user.DateTime, user.Admin, this, ((LocalUserInfo)user).PasswordHash);
						users[i] = tmp;
						DumpUsers(users);
						return tmp;
					}
				}
			}
			return null;
		}

		/// <summary>
		/// Sets the User/Administrator status of a User.
		/// </summary>
		/// <param name="user">The User.</param>
		/// <param name="admin">The status.</param>
		/// <returns>True if the User's status has been changed successfully.</returns>
		public UserInfo SetUserAdministrationStatus(UserInfo user, bool admin) {
			lock(locker) {
				UserInfo[] users = AllUsers;
				UsernameComparer comp = new UsernameComparer();
				for(int i = 0; i < users.Length; i++) {
					if(comp.Compare(users[i], user) == 0) {
						LocalUserInfo tmp = new LocalUserInfo(user.Username, user.Email, user.Active, user.DateTime, admin, this, ((LocalUserInfo)user).PasswordHash);
						users[i] = tmp;
						DumpUsers(users);
						return tmp;
					}
				}
			}
			return null;
		}

		/// <summary>
		/// Removes a User.
		/// </summary>
		/// <param name="user">The User to remove.</param>
		/// <returns>True if the User has been removed successfully.</returns>
		public bool RemoveUser(UserInfo user) {
			lock(locker) {
				UserInfo[] users = AllUsers;
				UsernameComparer comp = new UsernameComparer();
				int idx = -1;
				for(int i = 0; i < users.Length; i++) {
					if(comp.Compare(users[i], user) == 0) {
						idx = i;
						break;
					}
				}
				List<UserInfo> tmp = new List<UserInfo>(users);
				tmp.Remove(tmp[idx]);
				DumpUsers(tmp.ToArray());
			}
			return true;
		}

		/// <summary>
		/// Changes the Email address of a User.
		/// </summary>
		/// <param name="user">The User to change the Email address to.</param>
		/// <param name="newEmail">The new Email address.</param>
		/// <returns>The correct UserInfo object.</returns>
		public UserInfo ChangeEmail(UserInfo user, string newEmail) {
			lock(locker) {
				UserInfo[] users = AllUsers;
				UsernameComparer comp = new UsernameComparer();
				for(int i = 0; i < users.Length; i++) {
					if(comp.Compare(users[i], user) == 0) {
						users[i].Email = newEmail;
						DumpUsers(users);
						return users[i];
					}
				}
			}
			return null;
		}

		/// <summary>
		/// Changes the Password of a User.
		/// </summary>
		/// <param name="user">The User to change the Password to.</param>
		/// <param name="newPassword">The new Password (plain text).</param>
		/// <returns>The correct UserInfo object.</returns>
		public UserInfo ChangePassword(UserInfo user, string newPassword) {
			lock(locker) {
				UserInfo[] users = AllUsers;
				UsernameComparer comp = new UsernameComparer();
				for(int i = 0; i < users.Length; i++) {
					if(comp.Compare(users[i], user) == 0) {
						((LocalUserInfo)users[i]).PasswordHash = Hash.Compute(newPassword);
						DumpUsers(users);
						return users[i];
					}
				}
			}
			return null;
		}

		private void BackupUsersFile() {
			File.Copy(Settings.UsersFile,
				Settings.PublicDirectory + Path.GetFileNameWithoutExtension(Settings.UsersFile) +
				".bak" + Path.GetExtension(Settings.UsersFile), true);
		}

		/// <summary>
		/// Writes on disk all the Users.
		/// </summary>
		/// <param name="users">The User list.</param>
		/// <remarks>This method does not lock resources, therefore a lock is need in the caller.</remarks>
		private void DumpUsers(UserInfo[] users) {
			BackupUsersFile();

			StringBuilder sb = new StringBuilder();
			for(int i = 0; i < users.Length; i++) {
				LocalUserInfo u = (LocalUserInfo)users[i];
				sb.Append(u.Username);
				sb.Append("|");
				sb.Append(u.PasswordHash);
				sb.Append("|");
				sb.Append(u.Email);
				sb.Append("|");
				sb.Append(u.Active ? "ACTIVE" : "INACTIVE");
				sb.Append("|");
				sb.Append(u.DateTime.ToString("yyyy'/'MM'/'dd' 'HH':'mm':'ss"));
				sb.Append("|");
				sb.Append(u.Admin ? "ADMIN" : "USER");
				if(i != users.Length - 1) sb.Append("\r\n");
			}
			Tools.WriteFile(Settings.UsersFile, sb.ToString());
		}

	}

}
